﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Vita.Modules.Calendar {

  public enum RepeatType {
    None,
    Once,
    Interval, //ex: every hour
    Daily,
    Weekly,
    Monthly,
    Quarterly,
    Yearly,
    Linked, // linked to another event; ex: sending notifications ahead of the event
  }

  public enum DaySelectionType {
    None,
    Manual,     // Easter day - manually assigned each year
    FixedDay,     //might be negative, -1 means last day
    NthWeekday, // in combination with weekday mask; first monday every month; might be negative, -1 => last monday every month
    NthMonth,   //Quarterly, first month of quarter 
  }
  

  [Flags]
  public enum WeekDays {
    None = 0,
    Mon = 1,
    Tue = 1 << 1,
    Wed = 1 << 2,
    Thu = 1 << 3,
    Fri = 1 << 4,
    Sat = 1 << 5,
    Sun = 1 << 6,
    // combinations
    Workdays = Mon | Tue | Wed | Thu | Fri,
    Weekend = Sat | Sun,
    All = Workdays | Weekend,
  }

  [Flags]
  public enum DayFlags {
    None = 0,
    DayOff = 1, 
    GovDayOff = 1 << 1, 
    Holiday = 1 << 2,
    Birthday = 1 << 3,
  }

  [Flags]
  public enum AutoAdjustOptions {
    None = 0,
    WeekendOnly = 1, 
    WorkDayOnly = 1 << 1,
    // if does not fall on specified weekday, run on prior matching day 
    // ex: scheduled for 15th every month, on workdays; if falls on weekend, runs on prior Fri
    ShiftToPrior = 1 << 8,
    ShiftToNext = 1 << 9,
    ShiftToNearest = 1 << 10, //Veterans day
  }



  public class CalendarEventSchedule {
    public Guid Id;
    public string Name;
    public string Description; 
    public DateTime? StartsOn;
    public DateTime? EndsOn;
    public int MaxRuns;
    public RepeatType RepeatType;
    public DaySelectionType DaySelection;
    public int TimeshiftMinutes; //time shift from midnight, might be negative
    public WeekDays WeekdayMask;
    public int Month; 
    public int Day; // positive or negative
    public DayFlags Flags; 
    public AutoAdjustOptions AdjustOptions;
    public CalendarEventSchedule LinkedTo; 
  }

  public class CalendarSchedule {
    public string Name; 
    public readonly List<CalendarEventSchedule> Events = new List<CalendarEventSchedule>(); 
  }

  public class CalendarEvent {
    public DateTime Date;
    public CalendarEventSchedule Schedule;
  }

  public static class SchedulingExtensions {

    public static bool IsSet(this AutoAdjustOptions options, AutoAdjustOptions option) {
      return (options & option) != 0; 
    }
    public static bool IsSet(this WeekDays days, WeekDays day) {
      return (days & day) != 0;
    }

    public static CalendarSchedule GetUSHolidaySchedule() {
      // http://www.officeholidays.com/countries/usa/2016.php
      var days = new CalendarSchedule();
      days.AddDay("NewYearsDay", "New Years Day", DayFlags.DayOff, DaySelectionType.FixedDay, 1, 1);
      days.AddDay("MartinLutherKingDay", "Martin Luther King Day", DayFlags.GovDayOff, DaySelectionType.NthWeekday, 1, 3, WeekDays.Mon); // 3rd Monday in January
      days.AddDay("PresidentsDay", "Presidents Day", DayFlags.GovDayOff, DaySelectionType.NthWeekday, 2, 3, WeekDays.Mon); // 3rd Monday in Feb
      days.AddDay("MothersDay", "Mothers Day", DayFlags.Holiday, DaySelectionType.NthWeekday, 5, 2, WeekDays.Sun); // 2nd Sunday in May
      days.AddDay("MemorialDay", "Memorial Day", DayFlags.DayOff, DaySelectionType.NthWeekday, 5, -1, WeekDays.Mon); // Last Monday in May
      days.AddDay("FathersDay", "Fathers Day", DayFlags.Holiday, DaySelectionType.NthWeekday, 6, 3, WeekDays.Sun); // 2nd Sunday in May
      days.AddDay("IndependenceDay", "Independence Day", DayFlags.DayOff, DaySelectionType.FixedDay, 7, 4); // July 4
      days.AddDay("LaborDay", "Labor Day", DayFlags.DayOff, DaySelectionType.NthWeekday, 9, 3, WeekDays.Mon); // 1st Mon in September
      days.AddDay("ColumbusDay", "Columbus Day", DayFlags.Holiday, DaySelectionType.NthWeekday, 10, 2, WeekDays.Mon); // 2nd Mon in October
      days.AddDay("VeteransDay", "Veterans Day", DayFlags.Holiday, DaySelectionType.NthWeekday, 11, 11, WeekDays.None, 
                      AutoAdjustOptions.WorkDayOnly | AutoAdjustOptions.ShiftToNearest); // Week day nearest to Nov 11
      var thxG = days.AddDay("Thanksgiving", "Thanksgiving", DayFlags.DayOff, DaySelectionType.NthWeekday, 11, 4, WeekDays.Thu); // 4th Thursday in Nov
      var dayAFterThxG = days.AddLinkedDay(thxG, "DayAfterThanksgiving", "Day After Thanksgiving", DayFlags.DayOff, 1);
      days.AddDay("ChristmasDay", "Christmas Day", DayFlags.DayOff, DaySelectionType.FixedDay, 12, 25, options: AutoAdjustOptions.WorkDayOnly | AutoAdjustOptions.ShiftToNearest); 
        // Dec 25, or nearest day if falls on weekend

      return days; 
    }

    public static IList<CalendarEvent> GetEvents(this CalendarSchedule schedule, int year) {
      return null;  
    }

    public static CalendarEventSchedule AddDay(this CalendarSchedule schedule, string name, string description, DayFlags flags, DaySelectionType daySelection, 
                                                int month, int day, WeekDays weekDayMask = WeekDays.None, AutoAdjustOptions options = AutoAdjustOptions.None) {
      var mday = new CalendarEventSchedule() {Name = name, Description = description, RepeatType = RepeatType.Yearly, DaySelection = daySelection, Month = month, Day = day, AdjustOptions = options, Flags = flags};
      schedule.Events.Add(mday);
      return mday; 
    }

    public static CalendarEventSchedule AddLinkedDay(this CalendarSchedule schedule, CalendarEventSchedule linkedTo, string name, string description, DayFlags flags, int dayShift, AutoAdjustOptions options = AutoAdjustOptions.None) {
      var linkedDay = new CalendarEventSchedule() { Name = name, Description = description, RepeatType = RepeatType.Linked, DaySelection = DaySelectionType.None,Day = dayShift, AdjustOptions = options, LinkedTo = linkedTo};
      schedule.Events.Add(linkedDay);
      return linkedDay;
    }

  }

}
